1   /*
2    * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.security.jgss.krb5;
27  
28  import org.ietf.jgss.*;
29  import sun.security.jgss.*;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.io.IOException;
33  import java.io.ByteArrayOutputStream;
34  import sun.security.krb5.Confounder;
35  
36  /**
37   * This class represents a token emitted by the GSSContext.wrap()
38   * call. It is a MessageToken except that it also contains plaintext
39   * or encrypted data at the end. A wrapToken has certain other rules
40   * that are peculiar to it and different from a MICToken, which is
41   * another type of MessageToken. All data in a WrapToken is prepended
42   * by a random counfounder of 8 bytes. All data in a WrapToken is
43   * also padded with one to eight bytes where all bytes are equal in
44   * value to the number of bytes being padded. Thus, all application
45   * data is replaced by (confounder || data || padding).
46   *
47   * @author Mayank Upadhyay
48   */
49  class WrapToken extends MessageToken {
50      /**
51       * The size of the random confounder used in a WrapToken.
52       */
53      static final int CONFOUNDER_SIZE = 8;
54  
55      /*
56       * The padding used with a WrapToken. All data is padded to the
57       * next multiple of 8 bytes, even if its length is already
58       * multiple of 8.
59       * Use this table as a quick way to obtain padding bytes by
60       * indexing it with the number of padding bytes required.
61       */
62      static final byte[][] pads = {
63          null, // No, no one escapes padding
64          {0x01},
65          {0x02, 0x02},
66          {0x03, 0x03, 0x03},
67          {0x04, 0x04, 0x04, 0x04},
68          {0x05, 0x05, 0x05, 0x05, 0x05},
69          {0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
70          {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07},
71          {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}
72      };
73  
74      /*
75       * A token may come in either in an InputStream or as a
76       * byte[]. Store a reference to it in either case and process
77       * it's data only later when getData() is called and
78       * decryption/copying is needed to be done. Note that JCE can
79       * decrypt both from a byte[] and from an InputStream.
80       */
81      private boolean readTokenFromInputStream = true;
82      private InputStream is = null;
83      private byte[] tokenBytes = null;
84      private int tokenOffset = 0;
85      private int tokenLen = 0;
86  
87      /*
88       * Application data may come from an InputStream or from a
89       * byte[]. However, it will always be stored and processed as a
90       * byte[] since
91       * (a) the MessageDigest class only accepts a byte[] as input and
92       * (b) It allows writing to an OuputStream via a CipherOutputStream.
93       */
94      private byte[] dataBytes = null;
95      private int dataOffset = 0;
96      private int dataLen = 0;
97  
98      // the len of the token data: (confounder || data || padding)
99      private int dataSize = 0;
100 
101     // Accessed by CipherHelper
102     byte[] confounder = null;
103     byte[] padding = null;
104 
105     private boolean privacy = false;
106 
107     /**
108      * Constructs a WrapToken from token bytes obtained from the
109      * peer.
110      * @param context the mechanism context associated with this
111      * token
112      * @param tokenBytes the bytes of the token
113      * @param tokenOffset the offset of the token
114      * @param tokenLen the length of the token
115      * @param prop the MessageProp into which characteristics of the
116      * parsed token will be stored.
117      * @throws GSSException if the token is defective
118      */
119     public WrapToken(Krb5Context context,
120                      byte[] tokenBytes, int tokenOffset, int tokenLen,
121                      MessageProp prop)  throws GSSException {
122 
123         // Just parse the MessageToken part first
124         super(Krb5Token.WRAP_ID, context,
125               tokenBytes, tokenOffset, tokenLen, prop);
126 
127         this.readTokenFromInputStream = false;
128 
129         // Will need the token bytes again when extracting data
130         this.tokenBytes = tokenBytes;
131         this.tokenOffset = tokenOffset;
132         this.tokenLen = tokenLen;
133         this.privacy = prop.getPrivacy();
134         dataSize =
135             getGSSHeader().getMechTokenLength() - getKrb5TokenSize();
136     }
137 
138     /**
139      * Constructs a WrapToken from token bytes read on the fly from
140      * an InputStream.
141      * @param context the mechanism context associated with this
142      * token
143      * @param is the InputStream containing the token bytes
144      * @param prop the MessageProp into which characteristics of the
145      * parsed token will be stored.
146      * @throws GSSException if the token is defective or if there is
147      * a problem reading from the InputStream
148      */
149     public WrapToken(Krb5Context context,
150                      InputStream is, MessageProp prop)
151         throws GSSException {
152 
153         // Just parse the MessageToken part first
154         super(Krb5Token.WRAP_ID, context, is, prop);
155 
156         // Will need the token bytes again when extracting data
157         this.is = is;
158         this.privacy = prop.getPrivacy();
159         /*
160           debug("WrapToken Cons: gssHeader.getMechTokenLength=" +
161           getGSSHeader().getMechTokenLength());
162           debug("\n                token size="
163           + getTokenSize());
164         */
165 
166         dataSize =
167             getGSSHeader().getMechTokenLength() - getTokenSize();
168         // debug("\n                dataSize=" + dataSize);
169         // debug("\n");
170     }
171 
172     /**
173      * Obtains the application data that was transmitted in this
174      * WrapToken.
175      * @return a byte array containing the application data
176      * @throws GSSException if an error occurs while decrypting any
177      * cipher text and checking for validity
178      */
179     public byte[] getData() throws GSSException {
180 
181         byte[] temp = new byte[dataSize];
182         getData(temp, 0);
183 
184         // Remove the confounder and the padding
185         byte[] retVal = new byte[dataSize - confounder.length -
186                                 padding.length];
187         System.arraycopy(temp, 0, retVal, 0, retVal.length);
188 
189         return retVal;
190     }
191 
192     /**
193      * Obtains the application data that was transmitted in this
194      * WrapToken, writing it into an application provided output
195      * array.
196      * @param dataBuf the output buffer into which the data must be
197      * written
198      * @param dataBufOffset the offset at which to write the data
199      * @return the size of the data written
200      * @throws GSSException if an error occurs while decrypting any
201      * cipher text and checking for validity
202      */
203     public int getData(byte[] dataBuf, int dataBufOffset)
204         throws GSSException {
205 
206         if (readTokenFromInputStream)
207             getDataFromStream(dataBuf, dataBufOffset);
208         else
209             getDataFromBuffer(dataBuf, dataBufOffset);
210 
211         return (dataSize - confounder.length - padding.length);
212     }
213 
214     /**
215      * Helper routine to obtain the application data transmitted in
216      * this WrapToken. It is called if the WrapToken was constructed
217      * with a byte array as input.
218      * @param dataBuf the output buffer into which the data must be
219      * written
220      * @param dataBufOffset the offset at which to write the data
221      * @throws GSSException if an error occurs while decrypting any
222      * cipher text and checking for validity
223      */
224     private void getDataFromBuffer(byte[] dataBuf, int dataBufOffset)
225         throws GSSException {
226 
227         GSSHeader gssHeader = getGSSHeader();
228         int dataPos = tokenOffset +
229             gssHeader.getLength() + getTokenSize();
230 
231         if (dataPos + dataSize > tokenOffset + tokenLen)
232             throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
233                                    "Insufficient data in "
234                                    + getTokenName(getTokenId()));
235 
236         // debug("WrapToken cons: data is token is [" +
237         //      getHexBytes(tokenBytes, tokenOffset, tokenLen) + "]\n");
238 
239         confounder = new byte[CONFOUNDER_SIZE];
240 
241         // Do decryption if this token was privacy protected.
242 
243         if (privacy) {
244             cipherHelper.decryptData(this,
245                 tokenBytes, dataPos, dataSize, dataBuf, dataBufOffset);
246             /*
247             debug("\t\tDecrypted data is [" +
248                 getHexBytes(confounder) + " " +
249                 getHexBytes(dataBuf, dataBufOffset,
250                         dataSize - CONFOUNDER_SIZE - padding.length) +
251                 getHexBytes(padding) +
252             "]\n");
253             */
254 
255         } else {
256 
257             // Token data is in cleartext
258             // debug("\t\tNo encryption was performed by peer.\n");
259             System.arraycopy(tokenBytes, dataPos,
260                              confounder, 0, CONFOUNDER_SIZE);
261             int padSize = tokenBytes[dataPos + dataSize - 1];
262             if (padSize < 0)
263                 padSize = 0;
264             if (padSize > 8)
265                 padSize %= 8;
266 
267             padding = pads[padSize];
268             // debug("\t\tPadding applied was: " + padSize + "\n");
269 
270             System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
271                              dataBuf, dataBufOffset, dataSize -
272                              CONFOUNDER_SIZE - padSize);
273 
274            // byte[] debugbuf = new byte[dataSize - CONFOUNDER_SIZE - padSize];
275            // System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
276            //                debugbuf, 0, debugbuf.length);
277            // debug("\t\tData is: " + getHexBytes(debugbuf, debugbuf.length));
278         }
279 
280         /*
281          * Make sure sign and sequence number are not corrupt
282          */
283 
284         if (!verifySignAndSeqNumber(confounder,
285                                     dataBuf, dataBufOffset,
286                                     dataSize - CONFOUNDER_SIZE
287                                     - padding.length,
288                                     padding))
289             throw new GSSException(GSSException.BAD_MIC, -1,
290                          "Corrupt checksum or sequence number in Wrap token");
291     }
292 
293     /**
294      * Helper routine to obtain the application data transmitted in
295      * this WrapToken. It is called if the WrapToken was constructed
296      * with an Inputstream.
297      * @param dataBuf the output buffer into which the data must be
298      * written
299      * @param dataBufOffset the offset at which to write the data
300      * @throws GSSException if an error occurs while decrypting any
301      * cipher text and checking for validity
302      */
303     private void getDataFromStream(byte[] dataBuf, int dataBufOffset)
304         throws GSSException {
305 
306         GSSHeader gssHeader = getGSSHeader();
307 
308         // Don't check the token length. Data will be read on demand from
309         // the InputStream.
310 
311         // debug("WrapToken cons: data will be read from InputStream.\n");
312 
313         confounder = new byte[CONFOUNDER_SIZE];
314 
315         try {
316 
317             // Do decryption if this token was privacy protected.
318 
319             if (privacy) {
320                 cipherHelper.decryptData(this, is, dataSize,
321                     dataBuf, dataBufOffset);
322 
323                 // debug("\t\tDecrypted data is [" +
324                 //     getHexBytes(confounder) + " " +
325                 //     getHexBytes(dataBuf, dataBufOffset,
326                 // dataSize - CONFOUNDER_SIZE - padding.length) +
327                 //     getHexBytes(padding) +
328                 //     "]\n");
329 
330             } else {
331 
332                 // Token data is in cleartext
333                 // debug("\t\tNo encryption was performed by peer.\n");
334                 readFully(is, confounder);
335 
336                 if (cipherHelper.isArcFour()) {
337                     padding = pads[1];
338                     readFully(is, dataBuf, dataBufOffset, dataSize-CONFOUNDER_SIZE-1);
339                 } else {
340                     // Data is always a multiple of 8 with this GSS Mech
341                     // Copy all but last block as they are
342                     int numBlocks = (dataSize - CONFOUNDER_SIZE)/8 - 1;
343                     int offset = dataBufOffset;
344                     for (int i = 0; i < numBlocks; i++) {
345                         readFully(is, dataBuf, offset, 8);
346                         offset += 8;
347                     }
348 
349                     byte[] finalBlock = new byte[8];
350                     readFully(is, finalBlock);
351 
352                     int padSize = finalBlock[7];
353                     padding = pads[padSize];
354 
355                     // debug("\t\tPadding applied was: " + padSize + "\n");
356                     System.arraycopy(finalBlock, 0, dataBuf, offset,
357                                      finalBlock.length - padSize);
358                 }
359             }
360         } catch (IOException e) {
361             throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
362                                    getTokenName(getTokenId())
363                                    + ": " + e.getMessage());
364         }
365 
366         /*
367          * Make sure sign and sequence number are not corrupt
368          */
369 
370         if (!verifySignAndSeqNumber(confounder,
371                                     dataBuf, dataBufOffset,
372                                     dataSize - CONFOUNDER_SIZE
373                                     - padding.length,
374                                     padding))
375             throw new GSSException(GSSException.BAD_MIC, -1,
376                          "Corrupt checksum or sequence number in Wrap token");
377     }
378 
379 
380     /**
381      * Helper routine to pick the right padding for a certain length
382      * of application data. Every application message has some
383      * padding between 1 and 8 bytes.
384      * @param len the length of the application data
385      * @return the padding to be applied
386      */
387     private byte[] getPadding(int len) {
388         int padSize = 0;
389         // For RC4-HMAC, all padding is rounded up to 1 byte.
390         // One byte is needed to say that there is 1 byte of padding.
391         if (cipherHelper.isArcFour()) {
392             padSize = 1;
393         } else {
394             padSize = len % 8;
395             padSize = 8 - padSize;
396         }
397         return pads[padSize];
398     }
399 
400     public WrapToken(Krb5Context context, MessageProp prop,
401                      byte[] dataBytes, int dataOffset, int dataLen)
402         throws GSSException {
403 
404         super(Krb5Token.WRAP_ID, context);
405 
406         confounder = Confounder.bytes(CONFOUNDER_SIZE);
407 
408         padding = getPadding(dataLen);
409         dataSize = confounder.length + dataLen + padding.length;
410         this.dataBytes = dataBytes;
411         this.dataOffset = dataOffset;
412         this.dataLen = dataLen;
413 
414         /*
415           debug("\nWrapToken cons: data to wrap is [" +
416           getHexBytes(confounder) + " " +
417           getHexBytes(dataBytes, dataOffset, dataLen) + " " +
418           // padding is never null for Wrap
419           getHexBytes(padding) + "]\n");
420          */
421 
422         genSignAndSeqNumber(prop,
423                             confounder,
424                             dataBytes, dataOffset, dataLen,
425                             padding);
426 
427         /*
428          * If the application decides to ask for privacy when the context
429          * did not negotiate for it, do not provide it. The peer might not
430          * have support for it. The app will realize this with a call to
431          * pop.getPrivacy() after wrap().
432          */
433         if (!context.getConfState())
434             prop.setPrivacy(false);
435 
436         privacy = prop.getPrivacy();
437     }
438 
439     public void encode(OutputStream os) throws IOException, GSSException {
440 
441         super.encode(os);
442 
443         // debug("Writing data: [");
444         if (!privacy) {
445 
446             // debug(getHexBytes(confounder, confounder.length));
447             os.write(confounder);
448 
449             // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
450             os.write(dataBytes, dataOffset, dataLen);
451 
452             // debug(" " + getHexBytes(padding, padding.length));
453             os.write(padding);
454 
455         } else {
456 
457             cipherHelper.encryptData(this, confounder,
458                 dataBytes, dataOffset, dataLen, padding, os);
459         }
460         // debug("]\n");
461     }
462 
463     public byte[] encode() throws IOException, GSSException {
464         // XXX Fine tune this initial size
465         ByteArrayOutputStream bos = new ByteArrayOutputStream(dataSize + 50);
466         encode(bos);
467         return bos.toByteArray();
468     }
469 
470     public int encode(byte[] outToken, int offset)
471         throws IOException, GSSException  {
472 
473         // Token header is small
474         ByteArrayOutputStream bos = new ByteArrayOutputStream();
475         super.encode(bos);
476         byte[] header = bos.toByteArray();
477         System.arraycopy(header, 0, outToken, offset, header.length);
478         offset += header.length;
479 
480         // debug("WrapToken.encode: Writing data: [");
481         if (!privacy) {
482 
483             // debug(getHexBytes(confounder, confounder.length));
484             System.arraycopy(confounder, 0, outToken, offset,
485                              confounder.length);
486             offset += confounder.length;
487 
488             // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
489             System.arraycopy(dataBytes, dataOffset, outToken, offset,
490                              dataLen);
491             offset += dataLen;
492 
493             // debug(" " + getHexBytes(padding, padding.length));
494             System.arraycopy(padding, 0, outToken, offset, padding.length);
495 
496         } else {
497 
498             cipherHelper.encryptData(this, confounder, dataBytes,
499                 dataOffset, dataLen, padding, outToken, offset);
500 
501             // debug(getHexBytes(outToken, offset, dataSize));
502         }
503 
504         // debug("]\n");
505 
506         // %%% assume that plaintext length == ciphertext len
507         return (header.length + confounder.length + dataLen + padding.length);
508 
509     }
510 
511     protected int getKrb5TokenSize() throws GSSException {
512         return (getTokenSize() + dataSize);
513     }
514 
515     protected int getSealAlg(boolean conf, int qop) throws GSSException {
516         if (!conf) {
517             return SEAL_ALG_NONE;
518         }
519 
520         // ignore QOP
521         return cipherHelper.getSealAlg();
522     }
523 
524     // This implementation is way too conservative. And it certainly
525     // doesn't return the maximum limit.
526     static int getSizeLimit(int qop, boolean confReq, int maxTokenSize,
527         CipherHelper ch) throws GSSException {
528         return (GSSHeader.getMaxMechTokenSize(OID, maxTokenSize) -
529                 (getTokenSize(ch) + CONFOUNDER_SIZE) - 8); /* safety */
530     }
531 
532 }